#include "triangle.h"
#include <QDateTime>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>


const char *asteroidsInstancedVs =
        "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;"
        "layout (location = 2) in vec2 aTexCoords;"
        "layout (location = 3) in mat4 aInstanceMatrix;"

        "out vec2 TexCoords;"

        "uniform mat4 projection;"
        "uniform mat4 view;"

        "void main()"
        "{"
        "    TexCoords = aTexCoords;"
        "    gl_Position = projection * view * aInstanceMatrix * vec4(aPos, 1.0f);"
        "}";

const char *asteroidsInstancedFs =
        "#version 330 core\n"
        "out vec4 FragColor;"

        "in vec2 TexCoords;"

        "uniform sampler2D texture_diffuse1;"

        "void main()"
        "{"
        "    FragColor = texture(texture_diffuse1, TexCoords);"
        "}";

const char *planetVs =
        "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;"
        "layout (location = 2) in vec2 aTexCoords;"

        "out vec2 TexCoords;"

        "uniform mat4 projection;"
        "uniform mat4 view;"
        "uniform mat4 model;"

        "void main()"
        "{"
        "    TexCoords = aTexCoords;"
        "    gl_Position = projection * view * model * vec4(aPos, 1.0f);"
        "}";

const char *planetFs =
        "#version 330 core\n"
        "out vec4 FragColor;"

        "in vec2 TexCoords;"

        "uniform sampler2D texture_diffuse1;"

        "void main()"
        "{"
        "    FragColor = texture(texture_diffuse1, TexCoords);"
        "}";

Triangle::Triangle(){

}

Triangle::~Triangle(){

}

void Triangle::initializeGL(){
    this->resize(1000,800);
    //设置像素采样的样本个数，用于抗锯齿
    //着色器部分
    this->initializeOpenGLFunctions();
    // 监听键盘
    this->releaseKeyboard();

    // configure global opengl state
    // -----------------------------
    glEnable(GL_DEPTH_TEST);


    glEnable(GL_MULTISAMPLE);

    asteroidShader = std::make_shared<Shader>(this);
    planetShader = std::make_shared<Shader>(this);

    asteroidShader->readShaderStr(asteroidsInstancedVs, asteroidsInstancedFs);
    planetShader->readShaderStr(planetVs, planetFs);

    rock = std::make_shared<Model>("D:/Material/rock/rock.obj", this);
    planet = std::make_shared<Model>("D:/Material/planet/planet.obj", this);

    // generate a large list of semi-random model transformation matrices
    // ------------------------------------------------------------------
    glm::mat4* modelMatrices;
    modelMatrices = new glm::mat4[amount];
    srand(static_cast<unsigned int>(1000)); // initialize random seed
    float radius = 50.0;
    float offset = 10.0f;
    for (unsigned int i = 0; i < amount; i++)
    {
        glm::mat4 model = glm::mat4(1.0f);
        // 1. translation: displace along circle with 'radius' in range [-offset, offset]
        float angle = (float)i / (float)amount * 360.0f;
        float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float x = sin(angle) * radius + displacement;
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float y = displacement * 0.4f; // keep height of asteroid field smaller compared to width of x and z
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float z = cos(angle) * radius + displacement;
        model = glm::translate(model, glm::vec3(x, y, z));

        // 2. scale: Scale between 0.05 and 0.25f
        float scale = static_cast<float>((rand() % 20) / 100.0 + 0.05);
        model = glm::scale(model, glm::vec3(scale));

        // 3. rotation: add random rotation around a (semi)randomly picked rotation axis vector
        float rotAngle = static_cast<float>((rand() % 360));
        model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));

        // 4. now add to list of matrices
        modelMatrices[i] = model;
    }

    // configure instanced array
    // -------------------------
    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);

    // set transformation matrices as an instance vertex attribute (with divisor 1)
    // note: we're cheating a little by taking the, now publicly declared, VAO of the model's mesh(es) and adding new vertexAttribPointers
    // normally you'd want to do this in a more organized fashion, but for learning purposes this will do.
    // -----------------------------------------------------------------------------------------------------------------------------------
    for (unsigned int i = 0; i < rock->meshes.size(); i++)
    {
        unsigned int VAO = rock->meshes[i].VAO;
        glBindVertexArray(VAO);
        // set attribute pointers for matrix (4 times vec4)
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);
        glEnableVertexAttribArray(4);
        glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));
        glEnableVertexAttribArray(5);
        glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(2 * sizeof(glm::vec4)));
        glEnableVertexAttribArray(6);
        glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(3 * sizeof(glm::vec4)));

        glVertexAttribDivisor(3, 1);
        glVertexAttribDivisor(4, 1);
        glVertexAttribDivisor(5, 1);
        glVertexAttribDivisor(6, 1);

        glBindVertexArray(0);
    }
    cameraPos = glm::vec3(0.0f, 0.0f, 56.0f);
}

void Triangle::resizeGL(int w, int h){
    QExpandOpenGLWidget::resizeGL(w, h);
}

void Triangle::paintGL(){
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f));
    model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));

    asteroidShader->use();
    asteroidShader->setMat4("projection", projection);
    asteroidShader->setMat4("view", view);

    planetShader->use();
    planetShader->setMat4("projection", projection);
    planetShader->setMat4("view", view);

    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f));
    model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));
    planetShader->setMat4("model", model);
    planet->Draw(*planetShader);

    asteroidShader->use();
    asteroidShader->setInt("texture_diffuse1", 0);
    glActiveTexture(GL_TEXTURE0);
    // note: we also made the textures_loaded vector public (instead of private) from the model class.
    glBindTexture(GL_TEXTURE_2D, rock->textures_loaded[0].id);

    for (unsigned int i = 0; i < rock->meshes.size(); i++)
    {
        glBindVertexArray(rock->meshes[i].VAO);
        glDrawElementsInstanced(GL_TRIANGLES, static_cast<unsigned int>(rock->meshes[i].indices.size()), GL_UNSIGNED_INT, 0, amount);
        glBindVertexArray(0);
    }

    glUseProgram(0);
    this->update();
}

void Triangle::keyPressEvent(QKeyEvent *event){
    QExpandOpenGLWidget::keyPressEvent(event);
}

void Triangle::mouseReleaseEvent(QMouseEvent* event){
    QExpandOpenGLWidget::mouseReleaseEvent(event);
}


void Triangle::mousePressEvent(QMouseEvent *event) {
    QExpandOpenGLWidget::mousePressEvent(event);
}

void Triangle::mouseMoveEvent(QMouseEvent* event){
    QExpandOpenGLWidget::mouseMoveEvent(event);
}


void Triangle::wheelEvent(QWheelEvent *event) {
    QExpandOpenGLWidget::wheelEvent(event);
}
